home *** CD-ROM | disk | FTP | other *** search
- WARNING: This is an early specification for CAMD. Keep in mind that
- some of the information is out of date. See the includes and Autodocs
- for realtime.library and camd.library for the latest information.
-
- CAMD: Commodore Amiga Multimedia Driver
-
- CAMD is a general library for synchronizing different multimedia
- applications and passing data at high rates from one application to
- another in real time. The emphasis is on MIDI and music applications;
- However, CAMD can be used to synchronize other types of applications
- as well, such as laser disk, animation players, sound effects, CD Audio,
- videotape / SMPTE and other real-time applications.
-
- Issues being worked on:
-
- Named Rendezvous:
- The new architecture for CAMD will use the concept of named "rendezvous
- points". CAMD maintains a list of "names" which represent "meeting places"
- for real-time applications. (In the include files, a "meeting place" is
- referred to as a "cluster").
- Each application can have any number of input or output linkages. Each
- MidiLink structure will have the name of a "meeting place" which will be used
- to rendezvous with other applications at the same "meeting place". Any number
- of senders and receivers can meet at a single meeting place. Once the
- particpants arrive, the ports are "wired" together with pointers so that
- communication can be very fast and efficient. Any participant can leave at
- any time; When all participants have left, the meeting place is
- deleted. Senders may send "events" which are clumps of data in MIDI format.
- (Each "clump" contains one complete MIDI message). If there are no
- receivers, the messages go nowhere, otherwise each receiver gets a copy of
- the sender's event. Events are buffered, using memory allocated by the
- receiving application, or by CAMD.
- Some meeting places are "public" while others are "private". Public
- meeting places can be seen by the user -- for example, a MIDI user can see
- a scrolling list of meeting places, and select one for his application to
- rendezvous with. The user could also add a public meeting place to the list
- by clicking an "Add" button.
- Private meeting places do not appear on user lists. They are used for
- special types of communication between applications that do not wish to be
- disturbed. Privacy is actually an attribute of a participant -- the meeting
- place is only private if all participants are private. This allows for
- participants to discretely open a port at a meeting place which will only
- become public if another participant joins in. This avoids cluttering up
- user lists with features that are not active.
- The nice thing about the meeting place concept is that it is totally
- forgiving. If a user sets up his MIDI sequencer application to control a
- light-show application, using CAMD, he can save the configuration in the
- "Save Settings" menu. Then later, if he decides to run only one of those
- applications, it will still work, even though the other half of the link is
- not present. Thus, it will be easy for musicians and multimedia artists to
- create modular setups without having to spend tedious time linking and
- configuring their system because some software or equipment is not
- currently activated.
- Meeting places can also have a comment field, which is determined
- by the comment field of the MidiLink with the highest priority that
- is attached to it.
-
- Time structures for CAMD.
-
- Within the library base of CAMD is a central clock called the heartbeat.
- This clock is always counting, wrapping around when it overflows, at a rate
- of 500Hz. It is driven by one of the CIA timers (whichever one is available).
- From the heartbeat clock are derived the various "Conductors". Each
- conductor represents a group of applications which wish to remain
- synchronized together. Like the meeting places, conductors are created
- automatically when one member attempts to open it, and are deleted when the
- last member leaves. There is a default conductor called "main" which is
- used as a default for applications who want to be synchronized with
- whomever else is out there.
- Unlike meeting places, however, there is a limited number of 'senders',
- or in this case 'external time sources' such as SMPTE, MIDI Time Code, etc.
- There can be either zero or one external time sources per conductor. If
- there are zero, then it is a free-running clock, that is to say that it
- runs entirely from the heartbeat clock. If an external time source is
- present and active however, the conductor will still run off of the
- heartbeat clock, but the values will be constrained to be "near" the value
- of the external clock. Thus, the external clock keeps the internal clock in
- time, but is the internal clock that actually does the real work. Thus, we
- can "sing along" with SMPTE, but at a much higher clock rate and
- consequently much higher precision. After all, SMPTE only runs at 30 frames
- a second or so, while the heartbeat runs at 500 times a second.
- Conductors have essentially three attributes: A list of clients, called
- "players" a current time value, and a "play state", which indicates if the
- Conductors is stopped, paused, locating or running. These states will be
- defined in more detail later.
-
- Attached to the Conductor are any number of players. A PlayerInfo
- is a structure created by the application, which is used to deliver clock
- pulses from the conductor to the application. This is done through
- two mechanisms: A "wake-up call", where the application can be informed
- whenever a particular point in time passes, and a "tick hook" which allows
- for an application callback function to be invoked each time a new clock
- value is posted.
- In addition, there is a provision for applications to keep track of time
- in their own internal format. This is useful for music programs which may
- find measures and tempo (metered time) more useful than 500Hz ticks. In
- addition, CAMD allows for MIDI events posted to a particular port to be
- timestamped in the application's own format. Basically, each MidiNode
- contains a pointer to a longword value to use as the source of timestamping
- information. This could be used to point to any one of several fields
- in the PlayerInfo structure, depending on what type of timestamp was
- desired, or it could in fact point to any longword who's value is incremented
- on a regular basis.
- The PlayerInfo field also contains a facility for signaling a task
- whenver a change of state occurs, such as play, stop, etc.
-
- There is also a facility for distributing metered time between players.
- The flag PLAYERF_CONDUCTED is set for applications that want to take advantage
- of this feature. The application who's PlayerInfo node has the highest
- priority will determine the time for applications of lower priority.
- The first CONDUCTED player fills in the cdt_Metronome with the number
- of 384ths of a quarter note (unless someone has a beter number they want
- to use...) that have elapsed.
- Note that this only allows one "kind" of metered time to be distributed
- between players, namely musical time. Note that applications can still
- distribute MIDI Clocks to one another, if they need something special.
-
- A word of note on System overhead:
- A number of program authors are concerned because of the fact that some
- of the CIA timers run on a higher interrupt level than the Amiga serial
- port -- thus, potentially interfering with MIDI reception. Rest assured
- that CAMD will do most of it's work in a Soft Interrupt (the lowest level
- interrupt) and that the timer interrupt is extremely short.
-
- Clock States:
- The four states are: STOP, PAUSE, LOCATE and PLAY. The can be set by
- anybody at any time (even an interrupt). Thus, if one application signals
- to start playing, they all play together. Other applications which are
- driven from different Conductor are not affected, of course.
- An external timing source can also start the clock. This allows the
- user to start the playback simply by pressing the "play" button on the
- videotape recorder, laser disk, etc.
- There are two ways to start the clock: The first way is to set the
- clock to CLOCKSTATE_RUNNING. This starts the clock immediately, and then
- informs the applications that the clock has started, and what time it
- started at. This will most often be the case for an external time source,
- such as SMPTE -- it can't slow down the tape to wait for the applications
- to get ready.
- The other way is to set the state to CLOCKSTATE_LOCATE. This informs each
- player that the clock is about to start (and what time), then waits until
- each application has set its 'ready' bit. At that point, the state changes
- to CLOCKSTATE_RUNNING. The purpose of this is simple: Some applications can't
- start immediately, because of the fact that they have to set their current
- "state" based on the starting time. In the case of some music applications,
- this can take considerable time, as the application attempts to determine
- the "state" of all of it's internal variables for a particular clock time.
- So the CLOCKSTATE_LOCATE state allows simpler applications to wait until the
- more complex ones get ready.
- You may be asking: What happens when an external timing source sets the
- clock to CLOCKSTATE_RUNNING? What happens to these applications that can't
- start immediately? The answer is that they now have to "chase" the current
- clock time, like a man running after a train that is leaving the station,
- until they "catch up" at which point they will join in along with the
- others. This turns out to be no problem in practice, and professional
- sequencers do it all the time.
-
- More on External Synchronization Sources:
- An external timing source is a special kind of player. It sets a
- flag in it's PlayerInfo structure indicating that new clock times should
- never be posted. (In fact the only purpose of the PlayerInfo in this case
- is to keep the Conductor from being deleted in the case that all other
- PlayerInfos should go away).
- The software driver for the external input can, at any time, call the
- CAMD function ExternalSync(). This function constrains the Conductor to
- only allow clock values which are between the time of the current external
- clock tick and the estimated time of the next external clock tick. Each time
- an external timing pulse is received, it should call this function to allow
- the conductor to tick ahead a few more units. If the conductor falls
- behind, ExternalSync() will push it ahead to catch up with the external
- time, and if it pulls to far ahead, ExternalSync will cause it to stop and
- wait while the external clock catches up.
- I envision that external sync drivers will come as 3rd-party utilities
- with their own user interface. The utility will open a window which allows
- the user to set the following attributes:
-
- -- The name of the conductor to affect.
- -- A checkbox to enable or disable AutoStart
- -- A reading of the current external time value in whatever units
- appropriate, such as SMPTE time code
- -- an offset value, used to 'bias' the time (change the origin of zero)
- -- Various device-specific parameters.
-
- The "AutoStart" feature simply means that the external timing driver
- should start the clock when the external source starts, and should stop it
- when the external source stops.
- Note that when the external source is not running, the software driver
- can "release" the Conductor so that it becomes a free-running clock
- again. This allows users to play their melodies, test them out and edit
- them, while the tape is not running -- without having to press any special
- buttons.
- (Issue: If that's the case, does 'Autostart' have any meaning? Instead
- should we have a 'disable' button that allows them to play the tape without
- playing the music?)
- (Issue: We should also have a mode where the clock does NOT become
- a free-running clock when the tape is not running. This will allow us
- to have applications pre-located and "waiting" for the external clock
- to start).
-
- Private Conductors
- Sometimes, an application may which to have a free-running source-clock
- of it's own, so that it can get timing-based services unrelated to the
- externally-synchronized tracks. For example, dragging notes with the mouse
- may require a timing signal to shut off the note after so many seconds, and
- you would not want this to be controlled by the video tape -- in fact,
- you would want this to work even if the clock were stopped. How the
- application manages the complexities of dealing with more than one
- PlayerInfo is left as an excersize to the reader.
-
- Details of the Conductor
-
- Note that all time values can be negative. This is useful in cases
- where either the videotape has rewound back too far, or where the
- application wishes to provide a "count-in".
-
- cdt_ClockTime: This is the current value of the clock. It is
- incremented once each heartbeat, subject to external constraints. It will
- never go backwards while the clock is running -- if it does, then the clock
- is considered to have "stopped" and "rewound".
- Note: If the clock is being driven externally, ClockTime will not
- be incremented until the first external "tick" is received. This is to
- prevent the music from playing one tick's worth while waiting for the
- tape to start.
-
- cdt_StartTime: This is the starting time of the clock. It is set when
- the clock goes into either LOCATE or RUNNING state.
-
- cdt_ExternalTime: The clock time of the last external timing pulse.
- If external sync is enabled, the clock will never be earlier than this.
- Note that the external software driver will have to convert from it's units
- to 500Hz CAMD units.
-
- cdt_MaxExternalTime: The estimated clock time of the next external
- timing pulse. If the ClockTime is greater than this, it will not be
- incremented.
- (Note: Signed comparisons are used, in a "wrap-around" manner)
-
- cdt_Metronome: This is the metered time to be ditributed to applications
- that want it. The CONF_METROSET flag will be set if this field has been filled
- in. If it is not set, and the application is expecting metered time, then
- the application's time hook must fill it in itself, so that other players will
- be able to follow it's lead.
-
- cdt_PlayersNotReady: This is a count of the number of PlayerInfos
- that are not ready to go into RUNNING mode.
-
- cdt_State: This is the current playing state.
-
- cdt_Players: This is an Exec List of PlayerInfos.
-
- struct Conductor {
- struct Node cdt_Node; /* conductor list node */
- struct MinList cdt_Players; /* list of players */
- ULONG cdt_Flags; /* clock flags */
- ULONG cdt_ClockTime, /* current time of this sequence*/
- cdt_StartTime, /* start time of this sequence */
- cdt_ExternalTime, /* time from external unit */
- cdt_MaxExternalTime, /* upper limit on sync'd time */
- cdt_Metronome; /* metric time of highest pri node */
- UWORD cdt_PlayersNotReady; /* count of players not ready */
- UBYTE cdt_State; /* playing or stopped */
- UBYTE cdt_Pad[1];
- };
-
- #define CONDUCTF_EXTERNAL (1<<0) /* clock is externally driven */
- #define CONDUCTF_GOTTICK (1<<1) /* received 1st external tick */
- #define CONDUCTF_METROSET (1<<2) /* metered tie filled in */
-
- enum time_states {
- CLOCKSTATE_STOPPED=0, /* clock is stopped */
- CLOCKSTATE_PAUSED, /* clock is paused */
- CLOCKSTATE_LOCATE, /* go to 'running' when ready */
- CLOCKSTATE_RUNNING, /* run clock NOW */
- };
-
- Details of Players:
-
- pi_Hook: This is a pointer to the application's Callback function. The
- callback function is always called in the context of a software interrupt.
- (ISSUE: List calling parameters/registers for hooks)
- Also, the Hook structure conveniently contains a Node structure which
- is used to maintain a list of PlayerInfos.
-
- pi_Source: A pointer to the Conductor driving this clock.
-
- pi_Task: The task to signal when a state change occurrs.
- (ISSUE: What about non-task-based sequencers?)
-
- pi_StateSignal: The signal to use when signaling the task that the state
- has changed.
-
- pi_AlarmSignal: The signal to use when signaling the task that the alarm
- has gone off.
-
- pi_MetricTime: The current time, converted to the application's own
- metric. The flag PLAYERF_METRIC indicates whether this field is
- filled in by CAMD (to the conductor's current time) or filled in
- by the application (either by the tick hook or by the application's
- task).
-
- pi_AlarmTime: The clock time of when to call the hook function next.
-
- pi_Flags:
- PLAYERF_READY: Indicates that the player is ready to run.
- PLAYERF_WAIT: Indicates that the player is waiting for an alarm.
- PLAYERF_CONTINUOUS: Call the TickHook every cycle.
- PLAYERF_METRIC: Indicates that the tick hook will fill in the
- pi_MetricTime
- PLAYERF_NULL: This player is inactive, ignore it.
- PLAYERF_ALARMSIG: Use the signal to alarm the task, not the callback.
- PLAYERF_CONDUCTED: This application wants to receive metered time.
- The player must have a callback hook in CONTINUOUS mode.
-
- pi_UserData: For application use.
- pi_PlayerID: For application use.
-
- struct PlayerInfo {
- struct Node pi_Node; /* player node */
- struct Hook pi_Hook; /* callback for player */
- struct Conductor *pi_Source; /* pointer to parent context */
- struct Task *pi_Task; /* task to signal for changes */
- ULONG pi_StateSignal, /* signal to send for changes */
- pi_AlarmSignal; /* signal to send for alarms */
- LONG pi_MetricTime; /* current time in app's metric */
- LONG pi_AlarmTime, /* time to wake up */
- ULONG pi_Flags; /* general PlayerInfo flags */
- void *pi_UserData; /* for application use */
- LONG pi_PlayerID; /* for application use */
- };
-
- #define PLAYERF_READY (1<<0) /* player is ready to go! */
- #define PLAYERF_WAIT (1<<1) /* call hook when alarm goes off */
- #define PLAYERF_CONTINUOUS (1<<2) /* call hook every tick */
- #define PLAYERF_METRIC (1<<3) /* hook should fill in MetricTime */
- #define PLAYERF_NULL (1<<4) /* a dummy player, used for sync */
- #define PLAYERF_ALARMSIG (1<<5) /* use signal for alarm, not callback */
- #define PLAYERF_CONDUCTED (1<<6) /* give me metered time */
-
- Using the callback hook:
- If the CONDUCTED flag is set
- if the conductor's METROSET is NOT set:
- convert cdt_ClockTime to cdt_Metronome
- set METROSET
- convert cdt_Metronome to pi_MetricTime
- else if the PLAYERF_METRIC flag is set
- convert cdt_ClockTime to pi_MetricTime
- else ;
-
- if (MetricTime > (some time, say AlarmTime)) then wake up application...
-
- A note on MIDI Clocks:
-
- Since CAMD is a MIDI driver, it should be able to respond either to
- MIDI clocks or MIDI time code. Actually, it's probably not possible to
- interpret MIDI clocks, since they are really dependent on the application's
- interpretation of tempo. Instead, these should probably just be passed on
- to the application directly. We _might_ be able to help out by recording the
- time interval between MIDI clocks so that the application can interpolate
- the tempo.
- MIDI Time Code, on the other hand, is a lot like SMPTE. Basically,
- CAMD should be able to convert a MIDI Time Code event into a frame number
- internally, and drive the correct clock with it. It will need a library
- interface to set up the variables (autostart, etc.), although we _could_
- pass those things on to the application and let them handle it.
- In addition, the App should have the option of processing the MTC
- itself, in case they decide that they aren't using our clock.
-
- MIDI Start and Stop should be handled by the application, probably. It
- can decide if it wants to start the clock or not.
-
- Application-Specific Time:
- It is the job of the application to convert CAMD's clock times into
- it's own 'Metric', or local format, but CAMD provides some help in
- this regard.
- This section doesn't describe any specific features of CAMD, but
- rather contains some advice on how to get tempos that are accurate over
- long periods of time (important for film and video work).
- I have found that the most accurate way to convert time from one
- format to another is to not deal with the time values directly, but
- only with their delta-values. Basically, when a new clock time arrives,
- subtract it from the previous clock time to get the delta time.
- Then do the neccessary multiplication and division to get the converted
- delta time. Be careful to save any leftover remainders, and accumlate them
- into the total.
-
- Example:
-
- input_delta = clock_time - last_time;
- last_time = clock_time;
-
- output_delta = (input_delta * k1 + last_rmainder) / k2;
- last_remainder = (input_delta * k1 + last_remainder) % k2;
- /* (actually you would just do this in assembly and take
- the remainder of the divide).
- */
-
- local_time += output_delta;
-
- This method allows for expremely accurate timing over the long term.
- With this formula, assuming that tempos are stored in 32-bit fixed-point
- precision (16 bit integer, 16 bit fraction), you could have a song 23
- minutes long, and adjusting the tempo up or down by the smallest possible
- increment would allow you to change the timing of the final beat of the
- song by one film frame! (That's if I have my calculations right, of
- course). I think that this will satisfy the film-scoring people well enough.
-
- Note that the tempo conversion routines should probably _not_ be called
- when locating, as the locator will want to adjust the pi_MetricTime in
- leaps and jumps as it locates through the song.
-
- In addition, the TickHook should also do what's needed for actually
- playing back the notes. In my cases, I send a signal to a high-priority
- task, telling it to wake up and play the next batch of notes, if needed
- (actually, the UserData points to a structure which tells it at what metric
- time it should wake up the task... as a result, the task is not woken up
- uneccesarily).
-
- =================== Library Function Protypes ===========================
-
- /* ---- list access */
- void LockCAMD(ULONG);
- void UnlockCAMD(ULONG);
-
- /* ---- MidiNode */
- struct MidiNode *CreateMidiA(STRPTR, struct TagItem *);
- struct MidiNode *CreateMidi(STRPTR, Tag tag, ...);
- void DeleteMidi (struct MidiNode *);
- BOOL SetMidiAttrsA(struct MidiNode *, struct TagItem *);
- BOOL SetMidiAttrs(struct MidiNode *, Tag tag, ...);
- BOOL GetMidiAttr(struct MidiNode *, LONG, void *, LONG);
- struct MidiNode *NextMidi(struct MidiNode *);
- struct MidiNode *FindMidi(STRPTR);
- void FlushMidi (struct MidiNode *);
-
- /* ---- MidiLink */
- struct MidiLink *AddMidiLinkA(struct MidiNode *, STRPTR, LONG,
- struct TagItem *);
- struct MidiLink *AddMidiLink(struct MidiNode *, STRPTR, LONG, Tag tag, ...);
- BOOL SetMidiLinkAttrsA(struct MidiLink *, struct TagItem *);
- BOOL SetMidiLinkAttrs(struct MidiLink *, Tag tag, ...);
- BOOL GetMidiLinkAttr(struct MidiLink *, LONG, void *, LONG);
- void RemoveMidiLink(struct MidiLink *);
- struct MidiLink *NextClusterLink(struct MidiCluster *, struct MidiLink *, BOOL);
- struct MidiLink *NextNodeLink(struct MidiNode *, struct MidiLink *, BOOL);
- BOOL MidiLinkConnected(struct MidiLink *);
-
- /* ---- MidiCluster */
- struct MidiCluster *NextCluster(struct MidiCluster *);
- struct MidiCluster *FindCluster(STRPTR);
-
- /* ---- Message */
- void PutMidi (struct MidiLink *, LONG);
- void PutSysEx (struct MidiLink *, UBYTE *);
- BOOL GetMidi (struct MidiNode *, MidiMsg *);
- BOOL WaitMidi (struct MidiNode *, MidiMsg *);
- ULONG GetSysEx (struct MidiNode *, UBYTE *, ULONG);
- ULONG QuerySysEx (struct MidiNode *);
- void SkipSysEx (struct MidiNode *);
- UBYTE GetMidiErr (struct MidiNode *);
- WORD MidiMsgType (MidiMsg *);
- WORD MidiMsgLen (ULONG StatusByte);
-
- /* ---- Conductors */
- struct PlayerInfo *CreatePlayerA(STRPTR,STRPTR,struct TagItem *);
- struct PlayerInfo *CreatePlayer(STRPTR,STRPTR,Tag tag,...);
- void DeletePlayer(struct PlayerInfo *);
- BOOL SetPlayerAttrsA(struct Player *, struct TagItem *);
- BOOL SetPlayerAttrs(struct Player *,Tag tag,...);
- BOOL SetConductorState(struct Player *, LONG, LONG);
- BOOL ExternalSync(struct PlayerInfo *, LONG, LONG);
-
- /* ---- Devices */
- struct MidiDeviceData *OpenMidiDevice (UBYTE *Name);
- void CloseMidiDevice (struct MidiDeviceData *);
-
- =================== Some Important Structures ===========================
-
- /***************************************************************
- *
- * MidiLink -- links a cluster and a MidiNode
- *
- * All fields are READ ONLY. Modifications to fields may
- * performed only through the appropriate library function
- * calls.
- *
- ***************************************************************/
-
- struct MidiLink {
- struct Node ml_Node; /* node for cluster list */
- WORD ml_Pad;
- struct MinNode ml_OwnerNode; /* node for interface list */
- struct MidiNode *ml_MidiNode; /* interface we belong to... */
- struct MidiCluster *ml_Location; /* location we are a member of */
- char *ml_ClusterComment; /* comment for cluster */
- UBYTE ml_Flags; /* general flags */
- UBYTE ml_PortID; /* number of this port */
- UWORD ml_ChannelMask; /* mask flags for channel */
- ULONG ml_EventTypeMask; /* mask flags for events */
-
- /* Sys/Ex Filter */
- union SysExFilter
- {
- UBYTE b[4]; /* 1 byte mode, 3 bytes for match id(s) */
- ULONG sxf_Packed;
- } ml_SysExFilter;
-
- APTR ml_UserData; /* attached to events... */
- };
-
- /* Types of links (used for creation) */
-
- enum {
- MLTYPE_RECEIVER=0,
- MLTYPE_SENDER
- };
-
- #define MLF_SENDER (1<<0) /* this link sends from app */
- #define MLF_PARTCHANGE (1<<1) /* part change pending */
- #define MLF_PRIVATELINK (1<<2) /* make this link private */
-
- #define MLINK_Base TAG_USER+64
-
- #define MLINK_Location (MLINK_Base+1) /* rendezvous point */
- #define MLINK_ChannelMask (MLINK_Base+2) /* channel mask bits */
- #define MLINK_EventMask (MLINK_Base+3) /* event type mask bits */
- #define MLINK_UserData (MLINK_Base+4) /* user data for incoming events */
- #define MLINK_Comment (MLINK_Base+5) /* comment for cluster */
- #define MLINK_PortID (MLINK_Base+6) /* port id number for MidiMsg */
- #define MLINK_Private (MLINK_Base+7) /* not a public link */
- #define MLINK_Priority (MLINK_Base+8) /* priority of node */
- #define MLINK_SysExFilter (MLINK_Base+9) /* three 1-byte headers */
- #define MLINK_SysExFilterX (MLINK_Base+10) /* one 3-byte header */
-
- /***************************************************************
- *
- * MidiNode
- *
- * All fields are READ ONLY. Modifications to fields may
- * performed only through the appropriate library function
- * calls.
- *
- ***************************************************************/
-
- struct MidiNode
- {
- struct Node mi_Node; /* linked list node */
- UWORD mi_ClientType; /* type of application */
- struct Image *mi_Image; /* image for patch panel */
-
- struct MinList mi_OutLinks, /* list of output links */
- mi_InLinks; /* list of input links */
-
- struct Task *mi_SigTask; /* task to signal */
- struct Hook *mi_ReceiveHook, /* hook (and list node) */
- *mi_ParticipantHook; /* hook for participant change */
- BYTE mi_ReceiveSigBit, /* signalmask for new data */
- mi_ParticipantSigBit;/* signalmask for part change */
- UBYTE mi_ErrFilter; /* CMEF_ error filter for ErrFlags*/
- UBYTE mi_Alignment[1]; /* for longword alignment */
-
- ULONG *mi_TimeStamp; /* where timestamps come from */
-
- ULONG mi_MsgQueueSize, /* size of buffers */
- mi_SysExQueueSize;
-
- /* private stuff below here */
- };
-
- /* client types */
-
- #define CCType_Sequencer (1<<0)
- #define CCType_SampleEditor (1<<1)
- #define CCType_PatchEditor (1<<2)
- #define CCType_Notator (1<<3) /* transcription of MIDI notes */
- #define CCType_EventProcessor (1<<4) /* any data altering functions */
- #define CCType_EventFilter (1<<5)
- #define CCType_EventRouter (1<<6) /* e.g MIDI thru task */
- #define CCType_ToneGenerator (1<<7) /* i.e.using Amiga to make sound*/
- #define CCType_EventGenerator (1<<8) /* e.g algorithmic composition */
- #define CCType_GraphicAnimator (1<<9) /* e.g syncing animation software to MIDI */
-
-
- /* Tags for CreateMidi() and SetMidiAttrs() */
-
- #define MIDI_Base (TAG_USER+64)
- #define MIDI_Name (MIDI_Base+1) /* name of application */
- #define MIDI_SignalTask (MIDI_Base+2) /* task to signal if not this one*/
- #define MIDI_RecvHook (MIDI_Base+3) /* hook for incoming data */
- #define MIDI_PartHook (MIDI_Base+4) /* hook for participant change */
- #define MIDI_RecvSignal (MIDI_Base+5) /* signal to use if incoming */
- #define MIDI_PartSignal (MIDI_Base+6) /* signal to use if incoming */
- #define MIDI_MsgQueue (MIDI_Base+7) /* size of event buffer */
- #define MIDI_SysExSize (MIDI_Base+8) /* size of sysex buffer */
- #define MIDI_TimeStamp (MIDI_Base+9) /* timer to timestamp with */
- #define MIDI_ErrFilter (MIDI_Base+10) /* error filter bits */
- #define MIDI_ClientType (MIDI_Base+11) /* client type mask */
- #define MIDI_Image (MIDI_Base+12) /* application image */
-
- /* SysExFilter members */
- #define sxf_Mode b[0] /* SXFM_ mode bits */
- #define sxf_ID1 b[1] /* 3 1-byte id's or 1 3-byte id */
- #define sxf_ID2 b[2]
- #define sxf_ID3 b[3]
-
- =========================== Application Notes ============================
-
- Locating:
-
- On some applications, locating is very simple: Just take the current
- conductor time, convert to to a local MetricTime, and poke it into the
- correct variable.
- However, for some MIDI applications, life is not so simple. Because the
- playback of a sequence can cause many different state changes, both to
- external MIDI equipment and the sequencer's internal state, it is not easy
- to determine the total "state" of the sequencer for any particular time
- without actually playing the sequence. So, many sequencers do just that --
- they "play" the sequence (silently, with notes supressed) to themselves as
- fast as they can, until the silent playback reaches the point at which the
- current clock setting. At that point, they enable note playing and follow
- along with the other participants at normal speed.
- In a multi-track sequencer, this can be a complex process. One
- technique is, each time a track event is processed, the track with the next
- lowest non-processed event is processed. If the tracks are in the
- application's local metric (which they probably are) this can be even more
- complex. Either way, the "position time" is set to the time of the last
- processed event. This represents the clock time at which the sequencer is
- really positioned to at that instant.
- A potential problem lies in the fact that the time to do this is
- non-zero. Two things can happen: 1. The clock starts running before the
- sequencer is ready (before it has reached the start point), in which case
- the sequencer will have even further to go before it can start playing for
- real. In this case, the first couple of notes from that sequencer will be
- absent, since it was busy catching up and couldn't play them.
- The other situation is when the sequencer is busy trying to locate, and
- some other agency changes the clock setting out from under it. This is
- especially serious if reading from videotape and we have batches of
- non-continuous timecode (In other words, they haven't re-striped the tape
- after assembling the individual scenes). (Note that a "smart" time code
- reader could have translation tables to overcome this problem, but that's
- another product entirely).
- If the new clock setting is the same as the old one, then there's no
- problem. If the new clock setting is _later_ than the old one, then there's
- also no problem -- the sequencer can continue locating, but this time it
- just goes on past the old location and proceeds to the new one. If the new
- location is _slightly_ earlier than the old one, then it's a toss-up -- the
- sequencer could, if it wanted, simply declare "close enough" and flag
- itself as 'ready'. When the clock actually starts running, the sequencer
- will be waiting around a bit while the others catch up, since it got
- located too far ahead. When the others catch up, they will all be in sync.
- Note that this technique should _not_ be used when starting at the
- beginning of the song, since we want everybody together on the first note.
- If starting from the middle of a song, it's not so serious.
- Finally, if the new location is significantly earlier than the old
- location, the sequencer will have no choice but to start the location
- process all over again. Tough luck. (Fortunately, this case will be detected
- instantly, because the equation which terminates the location process
- (positiontime >= starttime) will suddenly and unexpectedly be true).
- What that means is simple: If, during a locate, someone changes the
- time, a new LOCATE or RUNNING signal will be sent to all applications...
- but if your application is still in the locating process, you can pretty
- much ignore it. (Use SetSignal to check and reset the signal...)
-
- Another problem arises in that for many music-applications, the
- relationship between the CAMD time and the applications local time format
- is not always linear. An example of this is tempo change, either "instant"
- tempo changes, or gradual tempo changes (accelerando and ritardando).
- Essentially, the relationship between musical time and real time
- is mathematically equivalent to taking the value of tempo (changing or
- not) and performing an integration on it, with respect to time. Doing
- this in real-time isn't always easy. It's especially difficult because
- the kinds of tempo changes the human ear likes to hear best are (e^x)
- curves -- the same curve you get when you have compound interest that
- is compounded continuously.
- Another way of looking at it is that the tempo should change linearly
- wth respect to METERED time, but not linearly with respect to REAL time.
- (Since the tempo is changing, the relationship between real and metered
- is not linear).
- Doing this during playback is actually pretty easy -- each time
- you get a new clock tick in the local meter, update the tempo by some
- fixed amount. It's during a locate where the situation gets tough.
- This is because there are lots of cases during the locate process where
- you will want to know the Conductor time for a given Metered Time
- (the reverse of the usual conversion). For example, if your next event
- in the queue is at some particular metered time, you want to know how far
- forward to wind the conductor to get there.
- Unfortunately, the simple summation technique described above only
- works in one direction. You can't use it to convert the other way...even
- in the simplest case (flat tempo) the numbers aren't going to come out
- exactly the same, which means that you are going to locate to just slightly
- the wrong place and be just slightly out of sync.
- The best advice I can give at this point is to study your logarithms.
- You can get good tempo changes by interpolating over a table of logs, and
- you can get very accurate logarithms in only a few machine instructions,
- if you take logs to the base 2.
-
- User Interface issues:
- What should the interface be for getting applications together on
- a Conductor group? What determines whether an application is independent
- or joins with a sync group?
- =========================================================================
- Issues that haven't been completely addressed: (Note that the old
- CAMD didn't address these issues either).
-
- Several people seem to want the ability to have a setup involving a
- filter type module, where if the filter is not present, it goes right
- "through" and connects directly to what the filter would have connected to.
- This becomes more complex when we think about clusters, because what
- the filter was connected "to" may be less well defined. Perhaps it's
- better to say "If this application is talking to nobody, then here's
- another place where we might find some listeners..."
- Another way to handle it might be to replace the filter with a
- "thru"-style connection, which gets supplanted by the application.
- So then what we need is 1) A utility that will establish "thru" links.
- 2) A way of making thru links be supressed whenever a certain other link
- is established.
-
- Which brings us to the second issue: Thru processing -- the ability to
- set up a direct copying process between two clusters. This can be done
- with a "callback" style of receiver, which just echoes each posting to the
- sender.
-